package net.homelinux.chaoswg.io.renga.krypto.gui.operations;

import java.math.BigInteger;
import java.util.List;

import net.homelinux.chaoswg.io.datatypes.Pair;
import net.homelinux.chaoswg.io.renga.core.converter.Converter;
import net.homelinux.chaoswg.io.renga.core.gui.operations.CompositeOperation;
import net.homelinux.chaoswg.io.renga.core.gui.operations.Control;
import net.homelinux.chaoswg.io.renga.core.gui.operations.FunctionInputOperation;
import net.homelinux.chaoswg.io.renga.core.gui.operations.Operation;
import net.homelinux.chaoswg.io.renga.core.gui.operations.helper.Connection;
import net.homelinux.chaoswg.io.renga.core.gui.operations.helper.ConversionException;
import net.homelinux.chaoswg.io.renga.core.gui.operations.visualisations.NamedBlockVisualisation;
import net.homelinux.chaoswg.io.renga.core.registries.ConverterRegistry;
import net.homelinux.chaoswg.io.renga.krypto.datatypes.numerical.UnsignedNBit;
import net.homelinux.chaoswg.io.renga.krypto.datatypes.numerical.UnsignedNBitFactory;

public class LoopOperation extends CompositeOperation {
    public LoopOperation () {
        this.visualisation = new NamedBlockVisualisation (0,0, 96, 96, "");

        this.inputs = 2;
        this.outputs = 1;

        this.connections = new Connection [2];
        connections[0] = new Connection ();
        connections[1] = new Connection ();
        
        visualisation.setInputs(inputs);
        visualisation.setOutputs(outputs);
    }
    
    public LoopOperation (final Control toViewAndControl) {
        super (toViewAndControl);

        this.control = toViewAndControl;

        this.visualisation = new NamedBlockVisualisation (0,0, 96, 96, "");

        this.inputs = 2;
        this.outputs = 1;

        this.connections = new Connection [2];
        connections[0] = new Connection ();
        connections[1] = new Connection ();

        visualisation.setInputs(inputs);
        visualisation.setOutputs(outputs);
    }

    @Override
    public void reset () {
        this.result = null;
        for (Operation op : control.getOperations()) {
            op.reset ();
        }
    }

    @SuppressWarnings("unchecked")
    @Override
    public Object apply(final int slot) {
        this.visualisation.setHighlighted(true);
        setChanged();
        notifyObservers(true);

        if (slot >= outputs) {
            return null;
        }
        
        final List<Operation> starts = control.getInputs();
        final List<Operation> ends  = control.getOutputs();
        
        FunctionInputOperation inputFromOutside = null;
        FunctionInputOperation numberOfIterations = null;
        
        for (final Operation op : starts) {
            final FunctionInputOperation tmp = ((FunctionInputOperation)op); 
            switch (tmp.getIsInputNumber()) {
                case 0 :
                    inputFromOutside = tmp; break;
                case 1 :
                    numberOfIterations = tmp; break;
            }
        }

        final Class inputType = numberOfIterations.apply(0).getClass();
        final Converter<UnsignedNBit, Object> c = ConverterRegistry.getConverterRegistry().getConverter(new Pair<Class, Class>(inputType,UnsignedNBit.class));
        
        if (c == null) {
            this.visualisation.setError(true);
            throw new ConversionException ("Can't find converter from " + inputType + " to " + UnsignedNBit.class);
        }
        
        BigInteger iterations = null; 
        try {
            iterations = c.apply(numberOfIterations.apply(0)).getValue();
            if (iterations == null) {
                this.visualisation.setError(true);
                throw new ConversionException ("Can't convert from " + numberOfIterations.apply(0) + " to " + UnsignedNBit.class + " on input 1");
            }
            
        } catch (Exception e) {
            this.visualisation.setError(true);
            throw new ConversionException ("Can't convert from " + numberOfIterations.apply(0) + " to " + UnsignedNBit.class + " on input 1");
        }
        
        final Object oldInputFromOutsideInitialValue = inputFromOutside.getInitialValue();
        final Object oldNumberOfIterationsInitialValue = numberOfIterations.getInitialValue();
        
        numberOfIterations.setInitialValue("0");
        inputFromOutside.setInitialValue(inputFromOutside.apply(0));
        result = inputFromOutside.getInitialValue();
        
        final UnsignedNBit iteration = UnsignedNBitFactory.createUnsignedNBitFactory().getLargestPossible();

        for (BigInteger i = new BigInteger ("0"); i.compareTo(iterations) < 0; i = i.add(new BigInteger ("1"))) {
            iteration.setValue(i);
            numberOfIterations.setInitialValue(iteration);
            result = ends.get(0).apply(0);
            
            inputFromOutside.setInitialValue(result);
        }
        
        inputFromOutside.setInitialValue(oldInputFromOutsideInitialValue);
        numberOfIterations.setInitialValue(oldNumberOfIterationsInitialValue);

        
        this.visualisation.setHighlighted(false);
        setChanged();
        notifyObservers(false);

        return result;
    }

    public String toString () {
        return this.getName() != null ? this.getName() :  "Loop";
    }
}
